#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "env.h"
#include "hash_table.h"
#include "vm_string.h"

int parse_type(char* input_str) {
    int const_type = OTHERS;
    if (strcmp(input_str, "i8") == 0) {
        const_type = I8;
    } else if (strcmp(input_str, "i16") == 0) {
        const_type = I16;
    } else if (strcmp(input_str, "i32") == 0) {
        const_type = I32;
    } else if (strcmp(input_str, "i64") == 0) {
        const_type = I64;
    } else if (strcmp(input_str, "f32") == 0) {
        const_type = F32;
    } else if (strcmp(input_str, "f64") == 0) {
        const_type = F64;
    } else if (strcmp(input_str, "char") == 0) {
        const_type = CHAR;
    } else if (strcmp(input_str, "string") == 0) {
        const_type = STRING;
    }
    return const_type;
}

void exec_calculator(vm_env* env, char calc_type, int type_1, vm_result param_1, int type_2, vm_result param_2) {
    const int USE_INT = 0;
    const int USE_DECIMAL = 1;
    const int USE_LONG = 2;
    int use = USE_INT;
    if (type_1 == F32 || type_1 == F64 || type_2 == F32 || type_2 == F64) {
        use = USE_DECIMAL;
    } else if (type_1 == I64 || type_2 == I64) {
        use = USE_LONG;
    }
    switch (use) {
        case 0:
            env->x_type = I32;
            int value1 = 0;
            int value2 = 0;
            if (type_1 == I8) {
                value1 = param_1.i8;
            } else if (type_1 == I16) {
                value1 = param_1.i16;
            } else if (type_1 == I32) {
                value1 = param_1.i32;
            }
            if (type_2 == I8) {
                value2 = param_2.i8;
            } else if (type_2 == I16) {
                value2 = param_2.i16;
            } else if (type_2 == I32) {
                value2 = param_2.i32;
            }
            if (calc_type == '+') {
                env->x.i32 = value1 + value2;
            } else if (calc_type == '-') {
                env->x.i32 = value1 - value2;
            } else if (calc_type == '*') {
                env->x.i32 = value1 * value2;
            } else if (calc_type == '/') {
                env->x.i32 = value1 / value2;
            } else if (calc_type == '%') {
                if (value2 == 0) {
                    printf("Cannot mod zero.");
                    exit(0);
                }
                env->x.i32 = value1 % value2;
            }
            break;
        case 1:
            env->x_type = F64;
            double value3 = 0;
            double value4 = 0;
            if (type_1 == I8) {
                value3 = param_1.i8;
            } else if (type_1 == I16) {
                value3 = param_1.i16;
            } else if (type_1 == I32) {
                value3 = param_1.i32;
            } else if (type_1 == I64) {
                value3 = param_1.i64;
            } else if (type_1 == F32) {
                value3 = param_1.f32;
            } else if (type_1 == F64) {
                value3 = param_1.f64;
            }
            if (type_2 == I8) {
                value4 = param_2.i8;
            } else if (type_2 == I16) {
                value4 = param_2.i16;
            } else if (type_2 == I32) {
                value4 = param_2.i32;
            } else if (type_2 == I64) {
                value4 = param_2.i64;
            } else if (type_2 == F32) {
                value4 = param_2.f32;
            } else if (type_2 == F64) {
                value4 = param_2.f64;
            }
            if (calc_type == '+') {
                env->x.f64 = value3 + value4;
            } else if (calc_type == '-') {
                env->x.f64 = value3 - value4;
            } else if (calc_type == '*') {
                env->x.f64 = value3 * value4;
            } else if (calc_type == '/') {
                env->x.f64 = value3 / value4;
            } else if (calc_type == '%') {
                printf("Decimal numbers cannot MOD.");
                exit(0);
            }
            break;
        case 2:
            env->x_type = I64;
            long value5 = 0;
            long value6 = 0;
            if (type_1 == I8) {
                value5 = param_1.i8;
            } else if (type_1 == I16) {
                value5 = param_1.i16;
            } else if (type_1 == I32) {
                value5 = param_1.i32;
            } else if (type_1 == I64) {
                value5 = param_1.i64;
            }
            if (type_2 == I8) {
                value6 = param_2.i8;
            } else if (type_2 == I16) {
                value6 = param_2.i16;
            } else if (type_2 == I32) {
                value6 = param_2.i32;
            } else if (type_2 == I64) {
                value6 = param_2.i64;
            }
            if (calc_type == '+') {
                env->x.i64 = value5 + value6;
            } else if (calc_type == '-') {
                env->x.i64 = value5 - value6;
            } else if (calc_type == '*') {
                env->x.i64 = value5 * value6;
            } else if (calc_type == '/') {
                env->x.i64 = value5 / value6;
            } else if (calc_type == '%') {
                if (value6 == 0) {
                    printf("Cannot mod zero.");
                    exit(0);
                }
                env->x.i64 = value5 % value6;
            }
            break;
    }
}

void exec_let(vm_env* env, char* name, int type, char* value, int val_size) {
    vm_var* new_node = (vm_var *) malloc(sizeof(vm_var) + 1);
    new_node->type = type;
    new_node->is_array = 0;
    switch (type) {
        case I8:
            new_node->value.i8 = atoi(value);
            break;
        case I16:
            new_node->value.i16 = atoi(value);
            break;
        case I32:
            new_node->value.i32 = atoi(value);
            break;
        case I64:
            new_node->value.i64 = atol(value);
            break;
        case F32:
            new_node->value.f32 = atof(value);
            break;
        case F64:
            new_node->value.f64 = atof(value);
            break;
        case CHAR:
            new_node->value.ch = value[0];
            break;
        case STRING:
            new_node->value.str = (void*)malloc(sizeof(char) * val_size + 1);
            memcpy(new_node->value.str, value, val_size);
            break;
        case OTHERS:
            new_node->value.others = (void*)malloc(val_size + 1);
            memcpy(new_node->value.others, value, val_size);
            break;
    }
    int result = hash_add(env->variables, name, new_node, sizeof(vm_hash_table_block));
    vm_hash_table_block* block = (vm_hash_table_block*)hash_find(env->variables, name);
    if (result == 0) {
        printf("Can't define variable %s.", name);
        exit(0);
    }
}

void exec_puts(vm_env* env, char* str) {
    if (str[0] == '#') {
        vm_hash_table_block* block = (vm_hash_table_block *) hash_find(env->variables, str + 1);
        vm_var* var = (vm_var *)(block->value);
        switch (var->type) {
            case I8:
                printf("%d", var->value.i8);
                break;
            case I16:
                printf("%d", var->value.i16);
                break;
            case I32:
                printf("%d", var->value.i32);
                break;
            case I64:
                printf("%lld", var->value.i64);
                break;
            case F32:
                printf("%f", var->value.f32);
                break;
            case F64:
                printf("%f", var->value.f64);
                break;
            case CHAR:
                printf("%c", var->value.ch);
                break;
            case STRING:
                printf("%s", var->value.str);
                break;
            case OTHERS:
                printf("[OBJECT]");
                break;
        }
        fflush(stdout);
    } else {
        printf("%s", str);
        fflush(stdout);
    }
}

void exec_puts_line(vm_env* env, char* str) {
    exec_puts(env, str);
    printf("\n");
}

void exec_puts_x(vm_env* env) {
    switch (env->x_type) {
        case I8:
            printf("%d", env->x.i8);
            break;
        case I16:
            printf("%d", env->x.i16);
            break;
        case I32:
            printf("%d", env->x.i32);
            break;
        case I64:
            printf("%lld", env->x.i64);
            break;
        case F32:
            printf("%f", env->x.f32);
            break;
        case F64:
            printf("%f", env->x.f64);
            break;
        case CHAR:
            printf("%c", env->x.ch);
            break;
        case STRING:
            printf("%s", env->x.str);
            break;
        case OTHERS:
            printf("[OBJECT]");
            break;
    }
}

char* read_last_attr(const char* buffer, int start) {
    char* attribute = (char*) malloc(sizeof(char) * 1024 + 1);
    int cursor = start;
    int cursor_2 = 0;
    while (buffer[cursor] != '\0' && buffer[cursor] != EOF) {
        attribute[cursor_2] = buffer[cursor];
        cursor_2++;
        cursor++;
    }
    attribute[cursor_2] = '\0';
    return str_ltrim(attribute);
}

char* read_next_attr(const char* buffer, int start, int* end) {
    char* attribute = (char*) malloc(sizeof(char) * 256 + 1);
    int cursor = start;
    int cursor_2 = 0;
    if (start > 1 && (buffer[start - 1] == '\0' || buffer[start - 1] == EOF)) {
        return NULL;
    }
    while (buffer[cursor] != '\0' && buffer[cursor] != ',' && buffer[cursor] != EOF) {
        attribute[cursor_2] = buffer[cursor];
        cursor_2++;
        cursor++;
    }
    attribute[cursor_2] = '\0';
    *end = cursor + 1;
    return str_trim(attribute);
}

int clone_x_to_new_var(vm_env* env, char* new_name) {
    vm_var* new_node = (vm_var *) malloc(sizeof(vm_var) + 1);
    new_node->type = env->x_type;
    new_node->is_array = 0;
    if (env->x_type == I8) {
        new_node->value.i8 = env->x.i8;
    } else if (env->x_type == I16) {
        new_node->value.i16 = env->x.i16;
    } else if (env->x_type == I32) {
        new_node->value.i32 = env->x.i32;
    } else if (env->x_type == I64) {
        new_node->value.i64 = env->x.i64;
    } else if (env->x_type == F32) {
        new_node->value.f32 = env->x.f32;
    } else if (env->x_type == F64) {
        new_node->value.f64 = env->x.f64;
    } else if (env->x_type == CHAR) {
        new_node->value.ch = env->x.ch;
    } else if (env->x_type == STRING) {
        new_node->value.str = env->x.str;
    } else if (env->x_type == OTHERS) {
        new_node->value.others = env->x.others;
    }
    return hash_add(env->variables, new_name, new_node, sizeof(vm_var));
}

void exec_raw_command(vm_env* env, char* buffer) {
    int end = 0;
    if (strncmp(buffer, "LET", 3) == 0) {
        char *name = read_next_attr(buffer, 6, &end);
        char *type = read_next_attr(buffer, end, &end);
        char *value = read_next_attr(buffer, end, &end);
        exec_let(env, name, parse_type(type), value, strlen(value));
        VM_FREE(name);
        VM_FREE(type);
        VM_FREE(value);
    } else if (strncmp(buffer, "PUTS_X", 6) == 0) {
        exec_puts_x(env);
    } else if (strncmp(buffer, "ADD", 3) == 0) {
        char *param_1 = read_next_attr(buffer, 6, &end);
        char *param_2 = read_next_attr(buffer, end, &end);
        if (param_1[0] != '#') {
            printf("Error occurred while parameter 1 must be a variable beginning with #.");
            return;
        }
        if (param_2[0] != '#') {
            printf("Error occurred while parameter 2 must be a variable beginning with #.");
            return;
        }
        vm_hash_table_block *var_1 = hash_find(env->variables, param_1 + 1);
        vm_hash_table_block *var_2 = hash_find(env->variables, param_2 + 1);
        if (var_1 == NULL) {
            printf("Error occurred while variable 1 %s couldn't be found.", param_1 + 1);
            return;
        }
        if (var_2 == NULL) {
            printf("Error occurred while variable 2 %s couldn't be found.", param_2 + 1);
            return;
        }
        exec_calculator(env, '+', ((vm_var *) (var_1->value))->type, ((vm_var *) (var_1->value))->value,
                        ((vm_var *) (var_2->value))->type, ((vm_var *) (var_2->value))->value);
        VM_FREE(param_1);
        VM_FREE(param_2);
    } else if (strncmp(buffer, "SUB", 3) == 0) {
        char *param_1 = read_next_attr(buffer, 6, &end);
        char *param_2 = read_next_attr(buffer, end, &end);
        if (param_1[0] != '#') {
            printf("Error occurred while parameter 1 must be a variable beginning with #.");
            return;
        }
        if (param_2[0] != '#') {
            printf("Error occurred while parameter 2 must be a variable beginning with #.");
            return;
        }
        vm_hash_table_block *var_1 = hash_find(env->variables, param_1 + 1);
        vm_hash_table_block *var_2 = hash_find(env->variables, param_2 + 1);
        if (var_1 == NULL) {
            printf("Error occurred while variable 1 %s couldn't be found.", param_1 + 1);
            return;
        }
        if (var_2 == NULL) {
            printf("Error occurred while variable 2 %s couldn't be found.", param_2 + 1);
            return;
        }
        exec_calculator(env, '-', ((vm_var *) (var_1->value))->type, ((vm_var *) (var_1->value))->value,
                        ((vm_var *) (var_2->value))->type, ((vm_var *) (var_2->value))->value);
        VM_FREE(param_1);
        VM_FREE(param_2);
    } else if (strncmp(buffer, "MUL", 3) == 0) {
        char *param_1 = read_next_attr(buffer, 6, &end);
        char *param_2 = read_next_attr(buffer, end, &end);
        if (param_1[0] != '#') {
            printf("Error occurred while parameter 1 must be a variable beginning with #.");
            return;
        }
        if (param_2[0] != '#') {
            printf("Error occurred while parameter 2 must be a variable beginning with #.");
            return;
        }
        vm_hash_table_block *var_1 = hash_find(env->variables, param_1 + 1);
        vm_hash_table_block *var_2 = hash_find(env->variables, param_2 + 1);
        if (var_1 == NULL) {
            printf("Error occurred while variable 1 %s couldn't be found.", param_1 + 1);
            return;
        }
        if (var_2 == NULL) {
            printf("Error occurred while variable 2 %s couldn't be found.", param_2 + 1);
            return;
        }
        exec_calculator(env, '*', ((vm_var *) (var_1->value))->type, ((vm_var *) (var_1->value))->value,
                        ((vm_var *) (var_2->value))->type, ((vm_var *) (var_2->value))->value);
        VM_FREE(param_1);
        VM_FREE(param_2);
    } else if (strncmp(buffer, "DIV", 3) == 0) {
        char *param_1 = read_next_attr(buffer, 6, &end);
        char *param_2 = read_next_attr(buffer, end, &end);
        if (param_1[0] != '#') {
            printf("Error occurred while parameter 1 must be a variable beginning with #.");
            return;
        }
        if (param_2[0] != '#') {
            printf("Error occurred while parameter 2 must be a variable beginning with #.");
            return;
        }
        vm_hash_table_block *var_1 = hash_find(env->variables, param_1 + 1);
        vm_hash_table_block *var_2 = hash_find(env->variables, param_2 + 1);
        if (var_1 == NULL) {
            printf("Error occurred while variable 1 %s couldn't be found.", param_1 + 1);
            return;
        }
        if (var_2 == NULL) {
            printf("Error occurred while variable 2 %s couldn't be found.", param_2 + 1);
            return;
        }
        exec_calculator(env, '/', ((vm_var *) (var_1->value))->type, ((vm_var *) (var_1->value))->value,
                        ((vm_var *) (var_2->value))->type, ((vm_var *) (var_2->value))->value);
        VM_FREE(param_1);
        VM_FREE(param_2);
    } else if (strncmp(buffer, "MOD", 3) == 0) {
        char *param_1 = read_next_attr(buffer, 6, &end);
        char *param_2 = read_next_attr(buffer, end, &end);
        if (param_1[0] != '#') {
            printf("Error occurred while parameter 1 must be a variable beginning with #.");
            return;
        }
        if (param_2[0] != '#') {
            printf("Error occurred while parameter 2 must be a variable beginning with #.");
            return;
        }
        vm_hash_table_block *var_1 = hash_find(env->variables, param_1 + 1);
        vm_hash_table_block *var_2 = hash_find(env->variables, param_2 + 1);
        if (var_1 == NULL) {
            printf("Error occurred while variable 1 %s couldn't be found.", param_1 + 1);
            return;
        }
        if (var_2 == NULL) {
            printf("Error occurred while variable 2 %s couldn't be found.", param_2 + 1);
            return;
        }
        exec_calculator(env, '%', ((vm_var *) (var_1->value))->type, ((vm_var *) (var_1->value))->value,
                        ((vm_var *) (var_2->value))->type, ((vm_var *) (var_2->value))->value);
        VM_FREE(param_1);
        VM_FREE(param_2);
    } else if (strncmp(buffer, "PUTS_L", 6) == 0) {
        char *param_1 = read_last_attr(buffer, 6);
        exec_puts_line(env, param_1);
        VM_FREE(param_1);
    } else if (strncmp(buffer, "PUTS", 4) == 0) {
        char *param_1 = read_last_attr(buffer, 6);
        exec_puts(env, param_1);
        VM_FREE(param_1);
    } else if (strncmp(buffer, "CAST", 4) == 0) {
        char *param_1 = read_next_attr(buffer, 6, &end);
        char *param_2 = read_next_attr(buffer, end, &end);
        int type = parse_type(param_1);
        vm_hash_table_block *block = (vm_hash_table_block *) hash_find(env->variables, param_2 + 1);
        if (param_2[0] != '#') {
            printf("Error occurred while missing # in parameter 2.");
            exit(0);
        }
        if (block == NULL) {
            printf("Cannot find variable %s.", param_2 + 1);
            exit(0);
        }
        vm_var *var = (vm_var *) (block->value);
        if (type == I8 || type == I16 || type == I32) {
            env->x_type = I32;
            if (var->type == I8) {
                env->x.i32 = (int) (var->value.i8);
            } else if (var->type == I16) {
                env->x.i32 = (int) (var->value.i16);
            } else if (var->type == I32) {
                env->x.i32 = (int) (var->value.i32);
            } else if (var->type == I64) {
                env->x.i32 = (int) (var->value.i64);
            } else if (var->type == F32) {
                env->x.i32 = (int) (var->value.f32);
            } else if (var->type == F64) {
                env->x.i32 = (int) (var->value.f64);
            }
        } else if (type == I64) {
            env->x_type = I64;
            if (var->type == I8) {
                env->x.i64 = (long long) (var->value.i8);
            } else if (var->type == I16) {
                env->x.i64 = (long long) (var->value.i16);
            } else if (var->type == I32) {
                env->x.i64 = (long long) (var->value.i32);
            } else if (var->type == I64) {
                env->x.i64 = (long long) (var->value.i64);
            } else if (var->type == F32) {
                env->x.i64 = (long long) (var->value.f32);
            } else if (var->type == F64) {
                env->x.i64 = (long long) (var->value.f64);
            }
        } else if (type == F32 || type == F64) {
            env->x_type = F64;
            if (var->type == I8) {
                env->x.f64 = (double) (var->value.i8);
            } else if (var->type == I16) {
                env->x.f64 = (double) (var->value.i16);
            } else if (var->type == I32) {
                env->x.f64 = (double) (var->value.i32);
            } else if (var->type == I64) {
                env->x.f64 = (double) (var->value.i64);
            } else if (var->type == F32) {
                env->x.f64 = (double) (var->value.f32);
            } else if (var->type == F64) {
                env->x.f64 = (double) (var->value.f64);
            }
        }
        VM_FREE(param_1);
        VM_FREE(param_2);
    } else if (strncmp(buffer, "CLN_X", 4) == 0) {
        char *param_1 = str_trim(read_last_attr(buffer, 6));
        clone_x_to_new_var(env, param_1);
        VM_FREE(param_1);
    } else if (strncmp(buffer, "ARRAY", 5) == 0) {
        char* array_name = read_next_attr(buffer, 6, &end);
        char* array_type = read_next_attr(buffer, end, &end);
        char* array_size = read_next_attr(buffer, end, &end);
        int array_var_type = parse_type(array_type);
        vm_var* array_var = (vm_var *) malloc(sizeof(vm_var) + 1);
        int real_array_size = 0;
        if (array_var_type == I8) {
            vm_array_result array_result;
            array_result.i8 = (char*) calloc(atoi(array_size), sizeof(char) + 1);
            array_var->array_data = array_result;
            real_array_size = atoi(array_size) * sizeof(char);
        } else if (array_var_type == I16) {
            vm_array_result array_result;
            array_result.i16 = (short*) calloc(atoi(array_size), sizeof(short) + 1);
            array_var->array_data = array_result;
            real_array_size = atoi(array_size) * sizeof(short);
        } else if (array_var_type == I32) {
            vm_array_result array_result;
            array_result.i32 = (int*) calloc(atoi(array_size), sizeof(int) + 1);
            array_var->array_data = array_result;
            real_array_size = atoi(array_size) * sizeof(int);
        } else if (array_var_type == I64) {
            vm_array_result array_result;
            array_result.i64 = (long long*) calloc(atoi(array_size), sizeof(long long) + 1);
            array_var->array_data = array_result;
            real_array_size = atoi(array_size) * sizeof(long long);
        } else if (array_var_type == F32) {
            vm_array_result array_result;
            array_result.f32 = (float*) calloc(atoi(array_size), sizeof(float) + 1);
            array_var->array_data = array_result;
            real_array_size = atoi(array_size) * sizeof(float);
        } else if (array_var_type == F64) {
            vm_array_result array_result;
            array_result.f64 = (double*) calloc(atoi(array_size), sizeof(double) + 1);
            array_var->array_data = array_result;
            real_array_size = atoi(array_size) * sizeof(double);
        } else if (array_var_type == CHAR) {
            vm_array_result array_result;
            array_result.ch = (char*) calloc(atoi(array_size), sizeof(char) + 1);
            array_var->array_data = array_result;
            real_array_size = atoi(array_size) * sizeof(char);
        } else if (array_var_type == STRING) {
            vm_array_result array_result;
            array_result.str = (char**) calloc(atoi(array_size), sizeof(char*) + 1);
            array_var->array_data = array_result;
            real_array_size = atoi(array_size) * sizeof(char*);
        }
        array_var->array_size = atoi(array_size);
        array_var->is_array = 1;
        array_var->type = array_var_type;
        hash_add(env->variables, array_name, array_var, real_array_size);
        VM_FREE(array_name);
        VM_FREE(array_type);
        VM_FREE(array_size);
    } else if (strncmp(buffer, "DEF", 3) == 0) {
        char* var_name = read_next_attr(buffer, 6, &end);
        char* element;
        vm_hash_table_block* block = (vm_hash_table_block *) hash_find(env->variables, var_name);
        vm_var* var = (vm_var *)(block->value);
        int i = 0;
        if (var->is_array == 0) {
            printf("DEF must be used upon an array.");
            exit(0);
        }
        while ((element = read_next_attr(buffer, end, &end)) != NULL) {
            if (i >= var->array_size) {
                printf("Array border overflown.");
                exit(0);
            }
            if (var->type == I8) {
                var->array_data.i8[i] = atoi(element);
            } else if (var->type == I16) {
                var->array_data.i16[i] = atoi(element);
            } else if (var->type == I32) {
                var->array_data.i32[i] = atoi(element);
            } else if (var->type == I64) {
                var->array_data.i64[i] = atoi(element);
            } else if (var->type == F32) {
                var->array_data.f32[i] = atof(element);
            } else if (var->type == F64) {
                var->array_data.f64[i] = atof(element);
            } else if (var->type == CHAR) {
                var->array_data.ch[i] = element[0];
            } else if (var->type == STRING) {
                var->array_data.str[i] = element;
            } else if (var->type == OTHERS) {
                // TODO
            }
            i++;
        }
    } else if (strncmp(buffer, "RESIZE", 6) == 0) {
        char* var_name = read_next_attr(buffer, 6, &end);
        int len = atoi(read_next_attr(buffer, end, &end));
        vm_hash_table_block* block = (vm_hash_table_block *) hash_find(env->variables, var_name);
        if (block == NULL) {
            printf("Cannot find variable %s.", var_name);
            exit(0);
        }
        vm_var* var = (vm_var *)(block->value);
        if (var->is_array == 0) {
            printf("RESIZE must be used upon an array.");
            exit(0);
        }
        vm_var* new_var_node;
        if (var->type == I8) {
            new_var_node = (vm_var *) malloc(sizeof(char) * len + 1);
            new_var_node->is_array = 1;
            new_var_node->array_size = len;
            new_var_node->type = var->type;
            new_var_node->array_data.i8 = (char*) malloc(sizeof(char) * len + 1);
            memcpy(new_var_node->array_data.i8, var->array_data.i8, sizeof(char) * len);
        } else if (var->type == I16) {
            new_var_node = (vm_var *) malloc(sizeof(short) * len + 1);
            new_var_node->is_array = 1;
            new_var_node->array_size = len;
            new_var_node->type = var->type;
            new_var_node->array_data.i16 = (short*) malloc(sizeof(short) * len + 1);
            memcpy(new_var_node->array_data.i16, var->array_data.i16, sizeof(short) * len);
        } else if (var->type == I32) {
            new_var_node = (vm_var *) malloc(sizeof(int) * len + 1);
            new_var_node->is_array = 1;
            new_var_node->array_size = len;
            new_var_node->type = var->type;
            new_var_node->array_data.i32 = (int*) malloc(sizeof(int) * len + 1);
            memcpy(new_var_node->array_data.i32, var->array_data.i32, sizeof(int) * len);
        } else if (var->type == I64) {
            new_var_node = (vm_var *) malloc(sizeof(long long) * len + 1);
            new_var_node->is_array = 1;
            new_var_node->array_size = len;
            new_var_node->type = var->type;
            new_var_node->array_data.i64 = (long long*) malloc(sizeof(long long) * len + 1);
            memcpy(new_var_node->array_data.i64, var->array_data.i8, sizeof(long long) * len);
        } else if (var->type == F32) {
            new_var_node = (vm_var *) malloc(sizeof(float) * len + 1);
            new_var_node->is_array = 1;
            new_var_node->array_size = len;
            new_var_node->type = var->type;
            new_var_node->array_data.f32 = (float*) malloc(sizeof(float) * len + 1);
            memcpy(new_var_node->array_data.f32, var->array_data.f32, sizeof(float) * len);
        } else if (var->type == F64) {
            new_var_node = (vm_var *) malloc(sizeof(double) * len + 1);
            new_var_node->is_array = 1;
            new_var_node->array_size = len;
            new_var_node->type = var->type;
            new_var_node->array_data.f64 = (double*) malloc(sizeof(double) * len + 1);
            memcpy(new_var_node->array_data.f64, var->array_data.f64, sizeof(double) * len);
        } else if (var->type == CHAR) {
            new_var_node = (vm_var *) malloc(sizeof(char) * len + 1);
            new_var_node->is_array = 1;
            new_var_node->array_size = len;
            new_var_node->type = var->type;
            new_var_node->array_data.ch = (char*) malloc(sizeof(char) * len + 1);
            memcpy(new_var_node->array_data.ch, var->array_data.ch, sizeof(char) * len);
        } else if (var->type == STRING) {
            new_var_node = (vm_var *) malloc(sizeof(char*) * len + 1);
            new_var_node->is_array = 1;
            new_var_node->array_size = len;
            new_var_node->type = var->type;
            new_var_node->array_data.str = (char**) malloc(sizeof(char*) * len + 1);
            memcpy(new_var_node->array_data.str, var->array_data.str, sizeof(char*) * len);
        } else if (var->type == OTHERS) {
            // TODO
        }
        hash_remove(env->variables, var_name);
        hash_add(env->variables, var_name, new_var_node, sizeof(vm_var));
    } else if (strncmp(buffer, "FILL", 4) == 0) {
        char* var_name = read_next_attr(buffer, 6, &end);
        char* element = read_next_attr(buffer, end, &end);
        vm_hash_table_block* block = (vm_hash_table_block *) hash_find(env->variables, var_name);
        if (block == NULL) {
            printf("Cannot find variable %s.", var_name);
            exit(0);
        }
        vm_var* var = (vm_var *)(block->value);
        int i = 0;
        if (var->is_array == 0) {
            printf("FILL command must be used upon an array.");
            exit(0);
        }
        while (i < var->array_size) {
            if (var->type == I8) {
                var->array_data.i8[i] = atoi(element);
            } else if (var->type == I16) {
                var->array_data.i16[i] = atoi(element);
            } else if (var->type == I32) {
                var->array_data.i32[i] = atoi(element);
            } else if (var->type == I64) {
                var->array_data.i64[i] = atoi(element);
            } else if (var->type == F32) {
                var->array_data.f32[i] = atof(element);
            } else if (var->type == F64) {
                var->array_data.f64[i] = atof(element);
            } else if (var->type == CHAR) {
                var->array_data.ch[i] = element[0];
            } else if (var->type == STRING) {
                var->array_data.str[i] = element;
            } else if (var->type == OTHERS) {
                // TODO
            }
            i++;
        }
    } else if (strncmp(buffer, "GET", 3) == 0) {
        char* var_name = read_next_attr(buffer, 6, &end);
        int element_id = atoi(read_next_attr(buffer, end, &end));
        vm_hash_table_block* block = (vm_hash_table_block *) hash_find(env->variables, var_name);
        if (block == NULL) {
            printf("Cannot find array %s.", var_name);
            exit(0);
        }
        vm_var* var = (vm_var *)(block->value);
        int i = 0;
        if (var->is_array == 0) {
            printf("GET must be used upon an array.");
            exit(0);
        }
        env->x_type = var->type;
        if (var->type == I8) {
            env->x.i8 = var->array_data.i8[element_id];
        } else if (var->type == I16) {
            env->x.i16 = var->array_data.i16[element_id];
        } else if (var->type == I32) {
            env->x.i32 = var->array_data.i32[element_id];
        } else if (var->type == I64) {
            env->x.i64 = var->array_data.i64[element_id];
        } else if (var->type == F32) {
            env->x.f32 = var->array_data.f32[element_id];
        } else if (var->type == F64) {
            env->x.f64 = var->array_data.f64[element_id];
        } else if (var->type == CHAR) {
            env->x.ch = var->array_data.ch[element_id];;
        } else if (var->type == STRING) {
            env->x.str = var->array_data.str[element_id];
        } else if (var->type == OTHERS) {
            // TODO
        }
    } else if (strncmp(buffer, "SET", 3) == 0) {
        char* var_name = read_next_attr(buffer, 6, &end);
        int element_id = atoi(read_next_attr(buffer, end, &end));
        char* value = read_next_attr(buffer, end, &end);
        vm_hash_table_block* block = (vm_hash_table_block *) hash_find(env->variables, var_name);
        if (block == NULL) {
            printf("Cannot find array %s.", var_name);
            exit(0);
        }
        vm_var* var = (vm_var *)(block->value);
        int i = 0;
        if (var->is_array == 0) {
            printf("SET must be used upon an array.");
            exit(0);
        }
        if (var->type == I8) {
            var->array_data.i8[element_id] = atoi(value);
        } else if (var->type == I16) {
            var->array_data.i16[element_id] = atoi(value);
        } else if (var->type == I32) {
            var->array_data.i32[element_id] = atoi(value);
        } else if (var->type == I64) {
            var->array_data.i64[element_id] = atoi(value);
        } else if (var->type == F32) {
            var->array_data.f32[element_id] = atof(value);
        } else if (var->type == F64) {
            var->array_data.f64[element_id] = atof(value);
        } else if (var->type == CHAR) {
            var->array_data.ch[element_id] = value[0];
        } else if (var->type == STRING) {
            var->array_data.str[element_id] = value;
        } else if (var->type == OTHERS) {
            // TODO
        }
    } else if (strncmp(str_trim(buffer), "DUMP", 4) == 0) {
        char* var_name = read_next_attr(buffer, 6, &end);
        vm_hash_table_block* block = (vm_hash_table_block *) hash_find(env->variables, var_name);
        if (block == NULL) {
            printf("Cannot find variable %s.", var_name);
            fflush(stdout);
            exit(0);
        }
        vm_var* var = (vm_var *)(block->value);
        int i = 0;
        if (var->is_array == 0) {
            printf("SET must be used upon an array.");
            exit(0);
        }
        printf("Dumping variable %s (size: %d):\n", var_name, var->array_size);
        while (i < var->array_size) {
            switch (var->type) {
                case I8:
                    printf("(%d): %d\n", i, var->array_data.i8[i]);
                    break;
                case I16:
                    printf("(%d): %d\n", i, var->array_data.i16[i]);
                    break;
                case I32:
                    printf("(%d): %d\n", i, var->array_data.i32[i]);
                    break;
                case I64:
                    printf("(%d): %lld\n", i, var->array_data.i64[i]);
                    break;
                case F32:
                    printf("(%d): %f\n", i, var->array_data.f32[i]);
                    break;
                case F64:
                    printf("(%d): %f\n", i, var->array_data.f64[i]);
                    break;
                case CHAR:
                    printf("(%d): %c\n", i, var->array_data.ch[i]);
                    break;
                case STRING:
                    printf("(%d): %s\n", i, var->array_data.str[i]);
                    break;
                case OTHERS:
                    printf("[OBJECT]\n");
                    break;
            }
            fflush(stdout);
            i++;
        }
    } else if (strcmp(str_trim(buffer), "") == 0) {
    } else if (strncmp(str_trim(buffer), "#", 1) == 0) {
    } else {
        printf("Unrecognized command.");
        exit(0);
    }
}

int main(int argc, char* argv[]) {
    FILE* file = fopen(argv[1], "r");
    char input_char;
    int cursor = 0;
    char buffer[102400];

    vm_env env;
    env.variables = hash_init(100);

    while (1) {
        input_char = fgetc(file);
        if (input_char == '\n') {
            buffer[cursor] = '\0';
            cursor = 0;
            exec_raw_command(&env, buffer);
            buffer[0] = '\0';
        } else if (input_char == EOF) {
            buffer[cursor] = '\0';
            cursor = 0;
            exec_raw_command(&env, buffer);
            break;
        } else {
            buffer[cursor] = input_char;
            cursor++;
        }
    }

    fclose(file);
    return 0;
}